package com.source3g.dubbo.config.boot;

import com.alibaba.dubbo.config.*;
import com.source3g.dubbo.config.boot.DubboAutoConfiguration.DubboServiceScanRegistrar;
import com.source3g.dubbo.config.spring.DubboReferenceBeanPostProcessor;
import com.source3g.dubbo.config.spring.DubboServiceBeanPostProcessor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;

import java.lang.annotation.*;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Created by jianglin on 2016/12/28
 */
@Configuration
@EnableConfigurationProperties(DubboProperties.class)
@Import({DubboServiceScanRegistrar.class, DubboReferenceBeanPostProcessor.class})
public class DubboAutoConfiguration {

    @Autowired
    private DubboProperties dubboProperties;

    // application

    @Bean
    @ConditionalOnMissingBean
    public ApplicationConfig applicationConfig() {

        return dubboProperties.getApplication();
    }

    // registry
    @Bean
    @ConditionalOnMissingBean
    public RegistryConfig registryConfig() {
        return dubboProperties.getRegistry();
    }

    // protocol

    @Bean
    @ConditionalOnMissingBean
    public ProtocolConfig protocolConfig() {
        return dubboProperties.getProtocol();
    }

    // monitor
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "spring.dubbo.monitor", name = "protocol")
    public MonitorConfig monitorConfig() {
        return dubboProperties.getMonitor();
    }

    // module
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "spring.dubbo.module", name = "name")
    public ModuleConfig moduleConfig() {
        return dubboProperties.getModule();
    }

    // provider
    @Bean
    public ProviderConfig providerConfig() {
        return dubboProperties.getProvider();
    }

    // consumer
    @Bean
    public ConsumerConfig consumerConfig() {
        return dubboProperties.getConsumer();
    }


    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(DubboServiceScanRegistrar.class)
    public @interface DubboComponentScan {
        @AliasFor("basePackages")
        String[] value() default {};

        @AliasFor("value")
        String[] basePackages() default {};

        Class<?>[] basePackageClasses() default {};
    }

    public static class DubboServiceScanRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
        private static final String BEAN_NAME = "dubboServiceBeanPostProcessor";

        private BeanFactory beanFactory;

        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            this.beanFactory = beanFactory;
        }

        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
            if (registry.containsBeanDefinition(BEAN_NAME)) {
                updatePostProcessor(registry, packagesToScan);
            } else {
                addPostProcessor(registry, packagesToScan);
            }
        }

        private void updatePostProcessor(BeanDefinitionRegistry registry,
                                         Set<String> packagesToScan) {
            BeanDefinition definition = registry.getBeanDefinition(BEAN_NAME);
            ConstructorArgumentValues.ValueHolder constructorArguments = definition.getConstructorArgumentValues()
                    .getGenericArgumentValue(Set.class);
            @SuppressWarnings("unchecked")
            Set<String> mergedPackages = (Set<String>) constructorArguments.getValue();
            mergedPackages.addAll(packagesToScan);
            constructorArguments.setValue(mergedPackages);
        }

        private void addPostProcessor(BeanDefinitionRegistry registry,
                                      Set<String> packagesToScan) {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(DubboServiceBeanPostProcessor.class);
            beanDefinition.getConstructorArgumentValues()
                    .addGenericArgumentValue(packagesToScan);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
        }

        private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
            Set<String> packagesToScan = new LinkedHashSet<String>();
            AnnotationAttributes attributes = AnnotationAttributes.fromMap(
                    metadata.getAnnotationAttributes(DubboComponentScan.class.getName()));
            if (attributes != null) {
                String[] basePackages = attributes.getStringArray("basePackages");
                Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
                packagesToScan.addAll(Arrays.asList(basePackages));
                for (Class<?> basePackageClass : basePackageClasses) {
                    packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
                }
            }
            if (packagesToScan.isEmpty()) {
                packagesToScan.addAll(AutoConfigurationPackages.get(this.beanFactory));
            }
            return packagesToScan;
        }
    }
}
